package main

import (
	"context"
	"crypto/tls"
	"sync"
	"time"

	"gitee.com/doraemon1996/bunny/goroutine"
	"gitee.com/doraemon1996/bunny/log"
	"gitee.com/doraemon1996/bunny/network"
	"gitee.com/doraemon1996/bunny/structure"
	"gitee.com/doraemon1996/bunny/timer"
	"github.com/gorilla/websocket"
)

type Client struct {
	structure.Control
	Encoder
	MemInfo

	locker  sync.RWMutex            // 连接管理器读写锁
	connMng map[uint64]network.Conn // 连接管理器
}

func (c *Client) Start() {
	c.Control.Start(nil)
}

// 清理连接
func (c *Client) Stop() {
	c.Control.Stop(func() {
		c.locker.Lock()
		mng := c.connMng
		c.connMng = make(map[uint64]network.Conn)
		c.locker.Unlock()

		for _, conn := range mng {
			conn.Stop(nil)
		}
	})
}

// 添加客户端连接
func (c *Client) OnConnect(conn network.Conn) {
	log.Infof("OnConnect %v", conn)

	if c.Closed() {
		log.Infof("OnConnect from %v, but Client closed. Will discard it", conn)
		conn.Stop(nil)
		return
	}

	c.locker.Lock()
	defer c.locker.Unlock()

	if _, exist := c.connMng[conn.ID()]; exist {
		log.Errorf("OnConnect %v exist, will discard the new one", conn)
		return
	}

	c.connMng[conn.ID()] = conn

	conn.Init(c.Context())
	conn.Start(c)
}

// 消息事件处理
func (c *Client) OnMessage(conn network.Conn, msg any) {
	length := 0
	switch val := msg.(type) {
	case string:
		length = len(val)
	case *network.UDPPacket:
		length = len(val.Message.(string))
	case *network.WSPacket:
		length = len(val.Message.(string))
	}
	log.Debugf("OnMessage from %v. Length: %v", conn, length)
}

// 定时任务事件处理
func (c *Client) OnCrontab(conn network.Conn) {
	log.Debugf("OnCrontab %v", conn)
}

// 错误事件处理
func (c *Client) OnError(conn network.Conn, err network.Error) bool {
	log.Errorf("OnError %v", err)

	return err.ErrorType() == network.ErrorType_Underlying
}

// 连接断开处理
func (c *Client) OnDisconnect(conn network.Conn, err network.Error) {
	if err != nil {
		log.Warnf("OnDisconnect abnormal disconnect %v with err: %s", conn, err)
	} else {
		log.Infof("OnDisconnect normal disconnect %v", conn)
	}

	c.locker.Lock()
	delete(c.connMng, conn.ID())
	c.locker.Unlock()

	switch conn.Type() {
	case network.Connection_WS, network.Connection_WSS:
		wsConn := conn.(network.ConnWS)
		wsConn.WriteClose(1000, "Bye! Bye!")
	}
}

func (c *Client) SendLoop(conn network.Conn, index, repeat uint64) {
	c.OnConnect(conn)
	if conn.Status() != network.Status_Opening {
		log.Errorf("SendLoop conn %v start failed", conn)
		return
	}

	defer func() {
		time.Sleep(time.Second)
		conn.Stop(nil)
	}()

	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	msgText := string(make([]byte, repeat))
	log.Infof("msg length: %v Byte", len(msgText))
	for count := 0; count < 10; count++ {
		if conn.Status() == network.Status_Closed {
			log.Warnf("%v %v force to exit", conn, index)
			break
		}

		<-ticker.C
		var pkt any
		switch conn.Type() {
		case network.Connection_TCP:
			pkt = msgText
		case network.Connection_UDP:
			pkt = &network.UDPPacket{Message: msgText}
		case network.Connection_WS, network.Connection_WSS:
			pkt = &network.WSPacket{Message: msgText, MessageType: websocket.TextMessage}
		}

		if err := conn.Send(pkt); err != nil {
			log.Errorf("%v send msg failed. err: %v", conn, err)
		}
	}
}

func RunClient(networkStr, addressStr, wsPath, wssCertPath, wssKeyPath string, clientCount, repeat uint64) {
	c := new(Client)
	c.Init(context.Background(), func() {
		c.connMng = make(map[uint64]network.Conn)
	})
	c.Start()

	var taskID uint64
	taskID = timer.Interval(time.Second, func() {
		if c.Opening() {
			c.PrintMemInfo()
		} else {
			timer.Remove(taskID)
		}
	})

	log.ExitHandlerInsert(func() { c.Stop() })

	optionList := make([]network.Option, 0)
	optionList = append(optionList, network.WithNetwork(networkStr))
	optionList = append(optionList, network.WithAddress(addressStr))
	optionList = append(optionList, network.WithWSPath(wsPath))

	if networkStr == "wss" {
		cert, err := tls.LoadX509KeyPair(wssCertPath, wssKeyPath)
		if err != nil {
			log.Fatalf("LoadX509KeyPair with %s %s failed. err: %v", wssCertPath, wssKeyPath, err)
		}
		tlsCfg := &tls.Config{
			Certificates:       []tls.Certificate{cert},
			InsecureSkipVerify: true,
		}
		optionList = append(optionList, network.WithWSTlsConfig(tlsCfg))
	}

	for i := uint64(0); i < clientCount; i++ {
		conn, err := network.Dial(c.Context(), optionList...)
		if err != nil {
			log.Fatalf("dial to %s %s failed. err: %v", networkStr, addressStr, err)
		} else {
			c.Add(1)
			goroutine.Submit(func() {
				defer c.Done()
				c.SendLoop(conn, i, repeat)
			})
		}
	}
}
